package com.gitee.ed.example.mybatis;

import cn.hutool.core.io.resource.ResourceUtil;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory;
import org.apache.ibatis.datasource.pooled.PooledDataSourceFactory;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import org.apache.ibatis.executor.loader.ProxyFactory;
import org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory;
import org.apache.ibatis.io.DefaultVFS;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.slf4j.Slf4jImpl;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.*;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
import org.apache.ibatis.type.EnumTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import javax.sql.DataSource;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * Mybatis 配置构建器
 *
 * @author hongda.li
 */
public class MybatisConfigurationBuilder {
    /**
     * Mybatis 配置类
     */
    private final Configuration configuration;
    /**
     * 自定义数据源
     */
    private DataSource dataSource;
    /**
     * 自定义事务工厂
     */
    private TransactionFactory transactionFactory;

    private MybatisConfigurationBuilder() {
        this.configuration = new Configuration();
    }

    private MybatisConfigurationBuilder(String resource) {
        this.configuration = new SqlSessionFactoryBuilder()
                .build(ResourceUtil.getStream(resource))
                .getConfiguration();
    }

    /**
     * 根据配置文件创建一个配置类构建器
     *
     * @return 配置类构建器
     */
    public static MybatisConfigurationBuilder newBuilder(String resource) {
        return new MybatisConfigurationBuilder(resource);
    }

    /**
     * 创建一个配置类构建器
     *
     * @return 配置类构建器
     */
    public static MybatisConfigurationBuilder newBuilder() {
        return new MybatisConfigurationBuilder()
                .slf4j()
                .setPooledDataSource()
                .setJdbcTransactionFactory()
                .setCacheEnabled(true)
                .setLazyLoadingEnabled(false)
                .setAggressiveLazyLoading(false)
                .setMultipleResultSetsEnabled(true)
                .setUseColumnLabel(true)
                .setUseGeneratedKeys(true)
                .setMapUnderscoreToCamelCase(true)
                .setSafeRowBoundsEnabled(false)
                .setSafeResultHandlerEnabled(true)
                .setCallSettersOnNulls(true)
                .setReturnInstanceForEmptyRow(true)
                .setVfsImpl(DefaultVFS.class)
                .setUseActualParamName(true)
                .setShrinkWhitespacesInSql(true)
                .setProxyFactory(new JavassistProxyFactory())
                .setAutoMappingBehavior(AutoMappingBehavior.PARTIAL)
                .setLocalCacheScope(LocalCacheScope.SESSION)
                .setJdbcTypeForNull(JdbcType.OTHER)
                .setLazyLoadTriggerMethods("equals", "clone", "hashCode", "toString")
                .setDefaultScriptingLanguage(XMLLanguageDriver.class)
                .setDefaultEnumTypeHandler(EnumTypeHandler.class)
                .setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.NONE)
                .setDefaultExecutorType(ExecutorType.SIMPLE);
    }

    /**
     * 将 Mybatis 的日志改为 Slf4j
     *
     * @return 链式调用
     */
    public MybatisConfigurationBuilder slf4j() {
        this.configuration.setLogImpl(Slf4jImpl.class);
        return this;
    }

    /**
     * 指定 MyBatis 增加到日志名称的前缀
     *
     * @param prefix 日志前缀
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setLogPrefix(String prefix) {
        this.configuration.setLogPrefix(prefix);
        return this;
    }

    /**
     * 自定义日志实现
     *
     * @param logImpl 自定义日志实现
     * @return 链式调用
     */
    public MybatisConfigurationBuilder logImpl(Class<? extends Log> logImpl) {
        this.configuration.setLogImpl(logImpl);
        return this;
    }

    /**
     * 设置 Mybatis 的代理工厂
     *
     * @param proxyFactory 代理工厂
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setProxyFactory(ProxyFactory proxyFactory) {
        this.configuration.setProxyFactory(proxyFactory);
        return this;
    }

    /**
     * 指定 VFS 的实现
     *
     * @param vfsImpl VFS 的实现
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setVfsImpl(Class<? extends VFS> vfsImpl) {
        this.configuration.setVfsImpl(vfsImpl);
        return this;
    }

    /**
     * 允许使用方法签名中的名称作为语句参数名称
     * 项目必须采用 Java 8 编译，并且加上 -parameters 选项
     *
     * @param enabled 是否启用
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setUseActualParamName(boolean enabled) {
        this.configuration.setUseActualParamName(enabled);
        return this;
    }

    /**
     * 是否允许 foreach 循环中的可迭代集合为空
     *
     * @param enabled 是否启用
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setNullableOnForEach(boolean enabled) {
        this.configuration.setNullableOnForEach(enabled);
        return this;
    }

    /**
     * 从 SQL中删除多余的空格字符
     *
     * @param enabled 是否启用
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setShrinkWhitespacesInSql(boolean enabled) {
        this.configuration.setShrinkWhitespacesInSql(enabled);
        return this;
    }

    /**
     * 设置属性配置
     *
     * @param key   键名
     * @param value 值
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setProperty(String key, String value) {
        Properties variables = this.configuration.getVariables();
        variables.setProperty(key, value);
        return this;
    }

    /**
     * 设置属性配置
     *
     * @param properties 批量设置属性
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setProperties(Map<String, String> properties) {
        Properties variables = this.configuration.getVariables();
        variables.putAll(properties);
        return this;
    }

    /**
     * 是否开启 Mybatis 缓存
     *
     * @param enabled 是否开启
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setCacheEnabled(boolean enabled) {
        this.configuration.setCacheEnabled(enabled);
        return this;
    }

    /**
     * 延迟加载的全局开关
     * 当开启时，所有关联对象都会延迟加载
     *
     * @param enabled 是否开启
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setLazyLoadingEnabled(boolean enabled) {
        this.configuration.setLazyLoadingEnabled(enabled);
        return this;
    }

    /**
     * 方法调用延迟加载的全局开关
     * 当开启时，任意方法的调用都会加载该对象的所有延迟加载属性
     *
     * @param enabled 是否开启
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setAggressiveLazyLoading(boolean enabled) {
        this.configuration.setAggressiveLazyLoading(enabled);
        return this;
    }

    /**
     * 是否允许单个语句返回多结果集（需要数据库驱动支持）
     *
     * @param enabled 是否开启
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setMultipleResultSetsEnabled(boolean enabled) {
        this.configuration.setMultipleResultSetsEnabled(enabled);
        return this;
    }

    /**
     * 是否允许使用列标签代替列名。实际表现依赖于数据库驱动
     *
     * @param enabled 是否开启
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setUseColumnLabel(boolean enabled) {
        this.configuration.setUseColumnLabel(enabled);
        return this;
    }

    /**
     * 允许 JDBC 支持自动生成主键，需要数据库驱动支持
     * 如果开启，将强制使用自动生成主键。尽管一些数据库驱动不支持此特性，但仍可正常工作
     *
     * @param enabled 是否开启
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setUseGeneratedKeys(boolean enabled) {
        this.configuration.setUseGeneratedKeys(enabled);
        return this;
    }

    /**
     * 是否开启驼峰命名自动映射
     *
     * @param enabled 是否开启
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setMapUnderscoreToCamelCase(boolean enabled) {
        this.configuration.setMapUnderscoreToCamelCase(enabled);
        return this;
    }

    /**
     * 是否允许在嵌套语句中使用分页 RowBounds
     *
     * @param enabled 是否允许
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setSafeRowBoundsEnabled(boolean enabled) {
        this.configuration.setSafeRowBoundsEnabled(enabled);
        return this;
    }

    /**
     * 是否允许在嵌套语句中使用结果处理器
     *
     * @param enabled 是否允许
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setSafeResultHandlerEnabled(boolean enabled) {
        this.configuration.setSafeResultHandlerEnabled(enabled);
        return this;
    }

    /**
     * 指定当结果集中值为 null 的时候是否调用映射对象的 setter
     *
     * @param enabled 是否允许
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setCallSettersOnNulls(boolean enabled) {
        this.configuration.setCallSettersOnNulls(enabled);
        return this;
    }

    /**
     * 当返回行的所有列都是空时，返回空实例或 null
     *
     * @param enabled 是否允许
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setReturnInstanceForEmptyRow(boolean enabled) {
        this.configuration.setReturnInstanceForEmptyRow(enabled);
        return this;
    }

    /**
     * 指定 MyBatis 应如何自动映射列到字段或属性
     * NONE 表示关闭自动映射
     * PARTIAL 只会自动映射没有定义嵌套结果映射的字段
     * FULL 会自动映射任何复杂的结果集（无论是否嵌套）
     *
     * @param behavior 映射行为
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setAutoMappingBehavior(AutoMappingBehavior behavior) {
        this.configuration.setAutoMappingBehavior(behavior);
        return this;
    }

    /**
     * MyBatis 利用本地缓存机制
     * 防止循环引用和加速重复的嵌套查询
     * SESSION 缓存一个会话中执行的所有查询
     * STATEMENT 本地缓存将仅用于执行语句，对相同 SqlSession 的不同查询将不会进行缓存
     *
     * @param cacheScope 缓存作用域
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setLocalCacheScope(LocalCacheScope cacheScope) {
        this.configuration.setLocalCacheScope(cacheScope);
        return this;
    }

    /**
     * 当没有为参数指定特定的 JDBC 类型时，空值的默认 JDBC 类型
     * 些数据库驱动需要指定列的 JDBC 类型
     *
     * @param jdbcType JDBC 类型
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setJdbcTypeForNull(JdbcType jdbcType) {
        this.configuration.setJdbcTypeForNull(jdbcType);
        return this;
    }

    /**
     * 指定对象的哪些方法触发一次延迟加载
     *
     * @param methodNames 方法名集合
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setLazyLoadTriggerMethods(String... methodNames) {
        this.configuration.setLazyLoadTriggerMethods(Set.of(methodNames));
        return this;
    }

    /**
     * 指定动态 SQL 生成使用的默认脚本语言
     *
     * @param driver 默认脚本语言类型
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setDefaultScriptingLanguage(Class<? extends LanguageDriver> driver) {
        this.configuration.setDefaultScriptingLanguage(driver);
        return this;
    }

    /**
     * 指定 Enum 使用的默认 TypeHandler
     *
     * @param typeHandler 类型处理器
     * @return 链式调用
     */
    @SuppressWarnings("rawtypes")
    public MybatisConfigurationBuilder setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler) {
        this.configuration.setDefaultEnumTypeHandler(typeHandler);
        return this;
    }

    /**
     * 指定发现自动映射目标未知列（或未知属性类型）的行为
     * NONE: 不做任何反应
     * WARNING: 输出警告日志
     * FAILING: 映射失败
     *
     * @param behavior 映射行为
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior behavior) {
        this.configuration.setAutoMappingUnknownColumnBehavior(behavior);
        return this;
    }

    /**
     * 配置默认的执行器
     * SIMPLE 就是普通的执行器
     * REUSE 执行器会重用预处理语句（PreparedStatement）
     * BATCH 执行器不仅重用语句还会执行批量更新
     *
     * @param executorType 映射行为
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setDefaultExecutorType(ExecutorType executorType) {
        this.configuration.setDefaultExecutorType(executorType);
        return this;
    }

    /**
     * 设置超时时间
     * 它决定数据库驱动等待数据库响应的秒数
     *
     * @param seconds 超时秒数
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setDefaultStatementTimeout(int seconds) {
        this.configuration.setDefaultStatementTimeout(seconds);
        return this;
    }

    /**
     * 结果集获取数量
     *
     * @param defaultFetchSize 获取数量
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setDefaultFetchSize(int defaultFetchSize) {
        this.configuration.setDefaultFetchSize(defaultFetchSize);
        return this;
    }

    /**
     * 指定语句默认的滚动策略
     *
     * @param resultSetType 滚动策略
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setDefaultFetchSize(ResultSetType resultSetType) {
        this.configuration.setDefaultResultSetType(resultSetType);
        return this;
    }

    /**
     * 设置内置池化的数据源
     *
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setPooledDataSource() {
        DataSourceFactory dataSourceFactory = new PooledDataSourceFactory();
        dataSourceFactory.setProperties(this.configuration.getVariables());
        return this.setDataSource(dataSourceFactory.getDataSource());
    }

    /**
     * 设置内置未池化的数据源
     *
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setUnPooledDataSource() {
        DataSourceFactory dataSourceFactory = new UnpooledDataSourceFactory();
        dataSourceFactory.setProperties(this.configuration.getVariables());
        return this.setDataSource(dataSourceFactory.getDataSource());
    }

    /**
     * 设置内置 Jndi 数据源
     *
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setJndiDataSource() {
        DataSourceFactory dataSourceFactory = new JndiDataSourceFactory();
        dataSourceFactory.setProperties(this.configuration.getVariables());
        return this.setDataSource(dataSourceFactory.getDataSource());
    }

    /**
     * 设置内置 JDBC 事务工厂
     *
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setJdbcTransactionFactory() {
        return this.setTransactionFactory(new JdbcTransactionFactory());
    }

    /**
     * 设置内置 Managed 事务工厂
     *
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setManagedTransactionFactory() {
        return this.setTransactionFactory(new ManagedTransactionFactory());
    }

    /**
     * 设置事务工厂
     *
     * @param transactionFactory 事务工厂
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setTransactionFactory(TransactionFactory transactionFactory) {
        this.transactionFactory = transactionFactory;
        return this;
    }

    /**
     * 设置数据源
     *
     * @param dataSource 数据源
     * @return 链式调用
     */
    public MybatisConfigurationBuilder setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
        return this;
    }

    /**
     * 添加一个 Mybatis 插件
     *
     * @param interceptor 插件
     * @return 链式调用
     */
    public MybatisConfigurationBuilder addInterceptor(Interceptor interceptor) {
        this.configuration.addInterceptor(interceptor);
        return this;
    }

    /**
     * 构建最终的配置
     *
     * @return Mybatis 配置
     */
    public Configuration build() {
        // 设置数据源和事务工厂
        if (this.dataSource != null && this.transactionFactory != null) {
            this.configuration.setEnvironment(new Environment.Builder(this.getClass().getName())
                    .dataSource(this.dataSource)
                    .transactionFactory(this.transactionFactory)
                    .build());
        }
        return this.configuration;
    }

    /**
     * 构建会话工厂
     *
     * @return SqlSession 会话工厂
     */
    public SqlSessionFactory buildSqlSessionFactory() {
        return new DefaultSqlSessionFactory(this.build());
    }
}
